package com.gopay.common.trace.def;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.gopay.common.trace.TraceLog;
import com.gopay.common.trace.TraceLogUtils;
import com.gopay.common.trace.enums.TraceType;
import com.gopay.common.trace.support.GenTraceId;
import com.gopay.common.trace.support.LogContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

/**
 * 跟踪类 -- 单例
 * @author ddh02
 * @Tracer
 * @2017/3/30 10:08  www.gopay.com.cn Inc.All rights reserved.
 */


public class Tracer {
    private static final Logger logger = LoggerFactory.getLogger(Tracer.class);

    private static Tracer tracer = null;

    /**
     * 注意springmvc 不要使用 这个变量
     */
    //传递parentSpan
   // private ThreadLocal<Span> spanThreadLocal = new ThreadLocal<Span>();

    //private GenerateTraceId generateTraceId = new GenerateTraceId(0L);


    private Tracer() {

    }

    /*public void removeParentSpan() {
        spanThreadLocal.remove();
    }

    public Span getParentSpan() {
        return spanThreadLocal.get();
    }

    public void setParentSpan(Span span) {
        spanThreadLocal.set(span);
    }*/

    public Span genSpan(String traceId, String pid, String spanname) {
        Span span = new Span();
        span.setId(GenTraceId.genUUID());
        span.setPid(pid);
        span.setName(spanname);
        span.setTid(traceId);
        return span;
    }

    public Span genSpan(String traceId, String pid, String spanname, TraceType traceType) {
        Span span = new Span();
        span.setId(GenTraceId.genUUID());
        span.setPid(pid);
        span.setName(spanname);
        span.setTid(traceId);
        span.setTy(traceType.toString());
        return span;
    }

    public Span genSpan(String traceId, String spanname, TraceType traceType) {
        Span span = new Span();
        span.setId(GenTraceId.genUUID());
        //span.setParentId(pid);
        span.setName(spanname);
        span.setTid(traceId);
        span.setTy(traceType.toString());
        return span;
    }

    public Span genSpan(String traceId, String pid, String id, String spanname) {
        Span span = new Span();
        span.setId(id);
        span.setPid(pid);
        span.setName(spanname);
        span.setTid(traceId);
        return span;
    }


    public Span newSpan(String spanname, TraceType traceType) {
        Span span = new Span();
        span.setTid(GenTraceId.genUUID());
        span.setId(GenTraceId.genUUID());
        span.setName(spanname);
        span.setTy(traceType.toString());
        return span;
    }

    public Span newSpan(String spanname, TraceType traceType, boolean idGen) {
        Span span = new Span();
        if (idGen) {
            span.setTid(GenTraceId.genUUID());
        }
        span.setId(GenTraceId.genUUID());
        span.setName(spanname);
        span.setTy(traceType.toString());
        return span;
    }

    public Span newSpan(String spanname) {
        Span span = new Span();
        span.setTid(GenTraceId.genUUID());
        span.setId(GenTraceId.genUUID());
        span.setName(spanname);
        return span;
    }

    public Endpoint newEndPoint() {
        return new Endpoint();
    }

    /*public static Tracer getTracer() {

        if (tracer == null) {
            synchronized (Tracer.class) {
                if (tracer == null) {
                    tracer = new Tracer();
                }
            }
        }
        return tracer;
    }*/

    private static class  TraceHolder{
        static Tracer instance=new Tracer();
    }
    public static Tracer getTracer() {
        return TraceHolder.instance;
    }



    /**
     * 注意：springmvc的不要使用这个方法
     * @param b
     */
    /*@Deprecated
    public void addBinaryAnntation(BinaryAnnotation b) {
        Span span = spanThreadLocal.get();
        if (span != null) {
            span.addBinaryAnnotation(b);
        }
    }*/


    //构件cs annotation
    public void clientSend(Span span, Endpoint endpoint, long start) {
        if (span == null) {
            return;
        }

        span.setSt(start);
        span.setEp(endpoint);
       /* Annotation annotation = new Annotation();
        annotation.setValue(Annotation.CLIENT_SEND);
        annotation.setTimestamp(start);
        annotation.setHost(endpoint);
        //span.addAnnotation(annotation);
        span.setCs(annotation);*/
    }

    //构件cr annotation
    public void clientReceive(Span span, Endpoint endpoint, long end) {
        if (span == null) {
            return;
        }

       // span.setEp(endpoint);
        span.setEt(end);
        /*Annotation annotation = new Annotation();
        annotation.setValue(Annotation.CLIENT_RECEIVE);
        annotation.setHost(endpoint);
        annotation.setTimestamp(end);
        //span.addAnnotation(annotation);

        span.setCr(annotation);*/

        //transfer.syncSend(span);
    }

    //构件sr annotation
    public void serverReceive(Span span, Endpoint endpoint, long start) {
        if (span == null) {
            return;
        }

        span.setSt(start);
        span.setEp(endpoint);

        /*Annotation annotation = new Annotation();
        annotation.setValue(Annotation.SERVER_RECEIVE);
        annotation.setHost(endpoint);
        annotation.setTimestamp(start);
        //span.addAnnotation(annotation);

        span.setSr(annotation);
        //spanThreadLocal.set(span);*/
    }

    //构件 ss annotation
    public void serverSend(Span span, Endpoint endpoint, long end) {

        if (span == null) {
            return;
        }

        span.setEt(end);
        //span.setEp(endpoint);

        /*Annotation annotation = new Annotation();
        annotation.setTimestamp(end);
        annotation.setHost(endpoint);
        annotation.setValue(Annotation.SERVER_SEND);
       // span.addAnnotation(annotation);

        span.setSs(annotation);*/

       // transfer.syncSend(span);

    }

    public void catchException(Throwable e, Endpoint ep, Span span) {

        if (span == null) {
            return;
        }
        BinaryAnnotation binaryAnnotation = new BinaryAnnotation();

        binaryAnnotation.setEndpoint(ep);
        binaryAnnotation.setType("ex");
        binaryAnnotation.setKey(span.getTy());
        binaryAnnotation.setValue(getThrowabelString(e));
        span.addBinaryAnnotation(binaryAnnotation);
    }


    /***
     * 将异常堆栈 转为字符串--***注意是否有性能问题
     * @param t
     * @return
     */
    private static String getThrowabelString(Throwable t) {
        long start = System.currentTimeMillis();

        StringWriter sw = new StringWriter();
        t.printStackTrace(new PrintWriter(sw, true));
        StringBuffer buffer = sw.getBuffer();

        //logger.warn("获取异常堆栈字符串 耗时={}, length={}", System.currentTimeMillis() - start , buffer.length()); //该日志测试完后 去掉TODO
        //add by zyt，如果异常堆栈过大，只截取前1024位
        if(buffer != null ){
            if(buffer.length() > 2000) {
                return buffer.substring(0, 2000).toString();
            }
            return buffer.toString();
        }
        return null;
    }



    public void showTree(Span span, int count, int level) {

        if (span == null) {
            return;
        }

       // TraceLogUtils.log(new TraceLog(TraceType.get(span.getTy()), calculateTime(span), span));

        int x = 0;
        for (Span s : span.getChilds()) {
            showTree(s, ++count, x++);
            --count;

        }
        span.setLe(count+1 + "-" + (level+1));
        TraceLog log =  new TraceLog.Builder(TraceType.get(span.getTy())).time(calculateTime(span)).message(span).build();
        //调整顺序  先执行的 先输出
        TraceLogUtils.log(log);

    }


    //自定义打印
    public void showTree(Span span, int count, StringBuilder sb){

        /*if (count == 0) {
            sb.append( String.format("%ntid=%s %n", span.getTid()));
        }

        sb.append(StringUtils.rightPad(" ", count * 4, "----"));

        //获取binaryann
        StringBuffer bas = new StringBuffer();
        if (span.getBa() != null && span.getBa().size() > 0) {
            for (BinaryAnnotation ba : span.getBa()) {
                bas.append("(" + ba.getType() + ",");
                bas.append(ba.getKey() + ",");
                bas.append(ba.getValue());
                bas.append(")");
            }
        }

        String host = "";
        if (TraceType.get(span.getTy()) == TraceType.SPRING_MVC) {
            Annotation sr = span.getSr();
            if (sr != null) {
                Endpoint p = sr.getHost();
                if (p != null) {
                    host = p.getIp() + ":" + p.getPort();
                }
            }
        }

        sb.append(String.format("(%s) %s[%s] [%s] [%s]ms [%s] %n",
                count,
                span.getSpanName(),
                host,
                TraceType.get(span.getTraceType()).name(),
                calculateTime(span),
                bas.toString())
        );
        for (Span s : span.getChilds()) {
            showTree(s, ++count, sb);
            --count;
        }
        if (count <= 0) {
            logger.info(sb.toString());
        }*/
    }

    /**
     * 判断是否结束 并打印 树形结构
     * @param span
     */
    public void endAndShow(Span span) {

        try {
            if (span == null) {
                return;
            }

            Integer count = LogContext.getContext().getCount();
            if (count <= 0) {
                showTree(span, 0, 0);
               // showTree(span, 0, new StringBuilder());
                //计数回归0 之后 ， 回收 threadlocal 资源
                LogContext.getContext().clearThreadLocal();
            }
        } catch (Exception e) {
            logger.error("", e);
        }

    }

    /**
     * @param span
     * @return
     */
    public long calculateTime(Span span) {

        if (span == null) {
            return -1;
        }

        if (true) {
            return span.getEt() - span.getSt();
        }

        TraceType type = TraceType.get(span.getTy());

        switch (type) {

            case LOCAL:
            /*case SPRING_MVC:
                return span.getSs().getTimestamp() - span.getSr().getTimestamp();

            case RPC:
            case HTTP_CLIENT:
                if (span.getCr() != null && span.getCs() != null) {
                    long crCs =  span.getCr().getTimestamp() - span.getCs().getTimestamp();
                    return crCs;
                }

                if (span.getSs() != null && span.getSr() != null) {
                    long ssSr =  span.getSs().getTimestamp() - span.getSr().getTimestamp();
                    return ssSr;
                }

                break;*/
                return 0;
            default:
                return -1;

        }


        /*StringBuffer sb = new StringBuffer();

        if (span.getCr() != null && span.getCs() != null) {
            long crCs =  span.getCr().getTimestamp() - span.getCs().getTimestamp();
           // sb.append("cr-cs客户端=" + crCs);
            sb.append(crCs);
        }

        if (span.getSs() != null && span.getSr() != null) {
            long ssSr =  span.getSs().getTimestamp() - span.getSr().getTimestamp();
           // sb.append("ss-sr服务端=" + ssSr);
            sb.append(ssSr);
        }

        if (span.getCr() != null && span.getCs() != null && span.getSs() != null && span.getSr() != null) {
            long srCs = span.getSr().getTimestamp() - span.getCs().getTimestamp();
            sb.append("sr-cs客户端到服务端=" + srCs);

            long crSs = span.getCr().getTimestamp() - span.getSs().getTimestamp();
            sb.append("cr-ss服务端到客户端" + crSs);
        }
        if (sb.length() > 0) {
            return sb.toString();
        }*/

       // return -1;
    }


    public static void main(String[] args) {
        System.out.println( StringUtils.leftPad("", 2*4, "----"));;

        Span span = new Span() ;
        span.setName("aaa");

        Span s1 = new Span();
        s1.setName("bbb");

        span.addChild(s1);

        SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
        filter.getExcludes().add("childs");
        filter.getExcludes().add("sample");
        String json = JSON.toJSONString(span, filter);

        System.out.println(json);

    }
}

